﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Axiom.Core;
using System.IO;
using Axiom.Graphics;
using Axiom.Math;
using Axiom.Configuration;
using Axiom.Collections;

namespace GameDesigner
{
    /// <summary>
    /// This class is designed to keep all Axiom engine needed configurations in one place. After 
    /// initialization of class either by useing a file or by using a default initiator user can call any 
    /// methods to configure the engine. 
    /// The engine managed by this class is however independent from this class and can be seperated from
    /// this class at any moment.
    /// All options contained in this class can be saved to a descriptor file at any moment by calling a
    /// save method.
    /// <br/>
    /// Following is the minimum required steps to have a complete configured engine using this class. 
    /// Every step's functionality can be skiped to be called from this class and replaced by user code.<br/>
    ///     1. LoadResourceLocations<br/>
    ///     2. SetupRenderSystem<br/>
    ///     3. CreateRenderWindow<br/>
    ///     4. InitializeResourceGroups<br/>
    ///     5. CreateScene<br/>
    ///     6. CreateDefaultCamera<br/>
    ///     7. LoadWorldGeometry<br/>
    /// </summary>
    partial class Descriptor
    {
        public const string ResourceMainFolder = "Resources";
        public const string TerrainDescriptorFileName = "EngineDescriptorTerrain.xml";

        #region Properties

        public string RenderSystemKey;
        List<ConfigOption> RenderSystemConfigs;

        public string SceneTypeName;

        public SceneType SceneType;

        public List<Resource> ResourcesList { get; set; }

        public int DefaultMipmapCount;

        public string Terrain;

        #endregion

        XmlNode sceneNodes = null;
        bool IsInitialized = false;

        public Descriptor()
            : this(null)
        { }

        /// <summary>
        /// Create new descriptor.
        /// </summary>
        /// <param name="AxiomEngine">Axiom Engine to be managed by this class. This most be created outside.</param>
        /// <param name="FileAddress">The address of an existing, valid descriptor file</param>
        public Descriptor(string FileAddress)
        {
            ResourcesList = new List<Resource>();
            RenderSystemConfigs = new List<ConfigOption>();

            if (!String.IsNullOrEmpty(FileAddress))
                LoadFromFile(FileAddress);
            else
                Initialize();
        }

        /// <summary>
        /// Tries to load descriptor options from given file. Any previous loaded options will be replaced by the present ones in file.
        /// </summary>
        /// <param name="FileAddress"></param>
        public void LoadFromFile(string FileAddress)
        {
            if (!File.Exists(FileAddress))
                throw new Exception("Descriptor file does not exists.");

            XmlDocument XML = new XmlDocument();
            XML.Load(FileAddress);

            XmlNode CoreNode = null;
            XmlNode ResourcesNode = null;
            XmlNode TerrainNode = null;
            sceneNodes = null;

            XmlNode RenderSystemNode = null;
            XmlNode SceneManagerTypeNode = null;
            XmlNode SceneManagerNameNode = null;

            if (XML.DocumentElement.Attributes["version"].Value != "0.8.0.0")
                throw new Exception("Descriptor file version is not supported.");

            //try finding needed nodes

            //find all root nodes
            foreach (XmlNode node in XML.DocumentElement.ChildNodes)
                if (node.Name.Equals("Core"))
                {
                    CoreNode = node;
                }
                else if (node.Name.Equals("Resources"))
                {
                    ResourcesNode = node;
                }
                else if (node.Name.Equals("Terrain"))
                {
                    TerrainNode = node;
                }
                else if (node.Name.Equals("Scene"))
                {
                    //the first XmlNode named node will be considered as RootSceneNode
                    foreach (XmlNode subNodes in node.ChildNodes)
                        if (subNodes.Name == "Node")
                        {
                            sceneNodes = subNodes;
                            break;
                        }
                }

            if (CoreNode == null)
                throw new Exception("Core configurations not found. This is not a valid Engine Descriptor file.");

            //find all core sub nodes
            foreach (XmlNode node in CoreNode.ChildNodes)
                if (node.Name.Equals("RenderSystem"))
                {
                    RenderSystemNode = node;
                }
                else if (node.Name.Equals("SceneManagerType"))
                {
                    SceneManagerTypeNode = node;
                }
                else if (node.Name.Equals("SceneManagerName"))
                {
                    SceneManagerNameNode = node;
                }

            if (RenderSystemNode == null || String.IsNullOrEmpty(RenderSystemNode.InnerText))
                throw new Exception("RenderSystem node not found. This is not a valid Engine Descriptor file.");

            this.RenderSystemKey = RenderSystemNode.Attributes["Key"].Value;
            foreach (XmlNode cfg in RenderSystemNode.ChildNodes)
            {
                XmlNode NameNode = null;
                XmlNode ValueNode = null;

                foreach (XmlNode chd in cfg.ChildNodes)
                    if (chd.Name == "Name")
                        NameNode = chd;
                    else if (chd.Name == "Value")
                        ValueNode = chd;

                if (NameNode != null && ValueNode != null)
                {
                    ConfigOption co = new ConfigOption(
                                NameNode.InnerText,
                                ValueNode.InnerText,
                                true);

                    RenderSystemConfigs.Add(co);
                }

            }

            //load SceneManagerType
            if (SceneManagerTypeNode == null || String.IsNullOrEmpty(SceneManagerTypeNode.InnerText))
                throw new Exception("SceneManagerType node not found. This is not a valid Engine Descriptor file.");

            this.SceneType = (SceneType)Enum.Parse(typeof(SceneType), SceneManagerTypeNode.InnerText);
            this.SceneTypeName = SceneManagerNameNode.InnerText;

            //Load resources
            string ResourceRoot = Path.GetDirectoryName(FileAddress);

            ResourcesList.Clear();
            if (ResourcesNode != null)
                foreach (XmlNode res in ResourcesNode.ChildNodes)
                    ResourcesList.Add(
                        new Resource(
                        //all paths will be converted to full path
                            ResourceRoot + Path.DirectorySeparatorChar + res.Attributes["Location"].Value,
                            res.Attributes["Type"].Value,
                            res.Attributes["Group"].Value,
                            bool.Parse(res.Attributes["Recursive"].Value)
                        )
                     );

            this.DefaultMipmapCount = int.Parse(ResourcesNode.Attributes["DefaultMipmapCount"].Value);

            //assign terrain
            if (TerrainNode != null)
                this.Terrain = TerrainNode.InnerText;

            IsInitialized = true;
            System.Diagnostics.Debug.Write("Descriptor loaded from file: " + FileAddress);
        }

        /// <summary>
        /// Resets the whole settings in Descriptor. Initiates the base needed settings with defaults.
        /// </summary>
        private void Initialize()
        {
            this.RenderSystemKey = "DirectX";

            this.SceneType = SceneType.Generic;

            this.DefaultMipmapCount = 5;

            IsInitialized = true;
            System.Diagnostics.Debug.Write("Descriptor initialized with default values.");
        }

        /// <summary>
        /// Adds resource locations defined in descriptor to underlaying engine
        /// </summary>
        public void LoadResourceLocations()
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            if (ResourceGroupManager.Instance != null)
                foreach (Resource res in ResourcesList)
                    ResourceGroupManager.Instance.AddResourceLocation(
                        res.Location,
                        res.Type,
                        res.Group,
                        res.Recursive,
                        false);
        }

        /// <summary>
        /// Creates and sets the underlying engine's RenderSystem based on defined options in descriptor
        /// </summary>
        public void SetupRenderSystem(Root AxiomEngine)
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            AxiomEngine.RenderSystem = AxiomEngine.RenderSystems[RenderSystemKey];
            foreach (ConfigOption cfg in RenderSystemConfigs)
                if (
                    AxiomEngine.RenderSystem.ConfigOptions.ContainsKey(cfg.Name) &&
                    AxiomEngine.RenderSystem.ConfigOptions[cfg.Name].PossibleValues.ContainsValue(cfg.Value)
                    )
                    AxiomEngine.RenderSystem.SetConfigOption(cfg.Name, cfg.Value);
        }

        /// <summary>
        /// Creates and initializes the underlaying engine's window based on Descriptor file's options
        /// </summary>
        public RenderWindow CreateRenderWindow(Root AxiomEngine)
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            //TODO: integrated window does not work
            return AxiomEngine.Initialize(true, "3D Window");

            //Engine.Initialize(false);
            //Axiom.Collections.NamedParameterList paramList = new Axiom.Collections.NamedParameterList();
            //paramList["externalWindowHandle"] = ctrlViewPort.Handle;
            //window =
            //    Engine.RenderSystem.CreateRenderWindow("RenderWindow", ctrlViewPort.Width, ctrlViewPort.Height, false, paramList);
        }

        /// <summary>
        /// Initializes all resource groups, related options are used from descriptor
        /// </summary>
        public void InitializeResourceGroups()
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            TextureManager.Instance.DefaultMipmapCount = DefaultMipmapCount;
            ResourceGroupManager.Instance.InitializeAllResourceGroups();
        }

        /// <summary>
        /// Creates SceneManager and loads the scene on to it based on dscriptor options. Does not load WorldGeometry.
        /// </summary>
        public SceneManager CreateScene(Root AxiomEngine)
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            SceneManager sceneManager;

            //TODO: scene manager can have a name, add it to configs
            if (String.IsNullOrEmpty(SceneTypeName))
                sceneManager = AxiomEngine.CreateSceneManager(SceneType);
            else
                sceneManager = AxiomEngine.CreateSceneManager(SceneTypeName);

            //load entities, nodes and attach them to scene
            if (sceneNodes != null)
            {
                CreateSceneNodeFromXml(sceneNodes, sceneManager.RootSceneNode, sceneManager);
            }

            return sceneManager;
        }

        private void AddSceneNodeObjectsFromXml(XmlNode ObjsNode, SceneNode Destination, SceneManager sceneManager)
        {
            foreach (XmlNode node in ObjsNode.ChildNodes)
            {
                NamedParameterList param = new NamedParameterList();

                foreach (XmlNode paramNode in node.ChildNodes)
                    if (paramNode.Name == "NamedParameter")
                        param.Add(paramNode.Attributes["Key"].Value, paramNode.Attributes["Value"].Value);

                MovableObject obj =
                    sceneManager.CreateMovableObject(
                            node.Attributes["Name"].Value,
                            node.Name,
                            param
                        );

                obj.CastShadows = bool.Parse(node.Attributes["CastShadows"].Value);

                Destination.AttachObject(obj);
            }
        }

        private SceneNode CreateSceneNodeFromXml(XmlNode Source, SceneNode newNode, SceneManager sceneManager)
        {
            if (newNode == null)
                newNode = sceneManager.CreateSceneNode(Source.Attributes["Name"].Value);

            foreach (XmlNode node in Source.ChildNodes)
            {
                //recursively create all subnodes
                if (node.Name == "Node")
                {
                    newNode.AddChild(CreateSceneNodeFromXml(node, null, sceneManager));
                }
                else if (node.Name == "Objects")
                {
                    AddSceneNodeObjectsFromXml(node, newNode, sceneManager);
                }
                else if (node.Name == "Position")
                {
                    Vector3 pos = new Vector3(
                        float.Parse(node.Attributes["X"].Value),
                        float.Parse(node.Attributes["Y"].Value),
                        float.Parse(node.Attributes["Z"].Value)
                        );

                    newNode.Position = pos;
                }
                else if (node.Name == "Scale")
                {
                    Vector3 sca = new Vector3(
                        float.Parse(node.Attributes["X"].Value),
                        float.Parse(node.Attributes["Y"].Value),
                        float.Parse(node.Attributes["Z"].Value)
                        );

                    newNode.Scale = sca;
                }
                else if (node.Name == "Orientation")
                {
                    Quaternion ori = new Quaternion(
                        float.Parse(node.Attributes["W"].Value),
                        float.Parse(node.Attributes["X"].Value),
                        float.Parse(node.Attributes["Y"].Value),
                        float.Parse(node.Attributes["Z"].Value)
                        );

                    newNode.Orientation = ori;
                }
            }

            return newNode;
        }

        /// <summary>
        /// Creates default camera defined in descriptor and it's related ViewPort
        /// </summary>
        /// <param name="window">Render window the camera will be created for</param>
        /// <returns></returns>
        public Camera CreateDefaultCamera(SceneManager TargetSceneManager, RenderWindow window)
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            //TODO: camera and viewport options to be moved to descriptor
            Camera camera = TargetSceneManager.CreateCamera("MainCamera");
            //Create viewport
            Viewport viewport = window.AddViewport(camera);
            camera.AspectRatio = (float)(viewport.ActualWidth / viewport.ActualHeight);
            viewport.BackgroundColor = Axiom.Core.ColorEx.Blue;

            //Set camera options
            camera.Position = new Vector3(100f, 300f, 200f);
            camera.Pitch(-45f);
            camera.Near = 5f;

            return camera;
        }

        /// <summary>
        /// Loads World Geometry based on options defined in descriptor
        /// </summary>
        public void LoadWorldGeometry(SceneManager TargetSceneManager)
        {
            if (!IsInitialized)
                throw new Exception("Descriptor needs to be initialized before use.");

            if (!String.IsNullOrEmpty(Terrain))
                TargetSceneManager.LoadWorldGeometry(Terrain);
        }
    }
}
